package com.ganzi.ganziserver.shiro;

import com.ganzi.ganziserver.shiro.filter.KickoutSessionControlFilter;
import com.ganzi.ganziserver.shiro.filter.MyShiroFilterForCors;
import com.ganzi.ganziserver.shiro.redis.*;
import com.ganzi.ganziserver.shiro.redis.OtherCache.OtherCacheManager;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.resource.ResourceUrlProvider;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.Filter;

@Configuration
public class ShiroConfiguration {

    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, Filter> filterMap = shiroFilterFactoryBean.getFilters();
        filterMap.put("authc", new MyShiroFilterForCors());
        filterMap.put("kickout", kickoutSessionControlFilter());//限制同一帐号同时在线的个数
        shiroFilterFactoryBean.setFilters(filterMap);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        filterChainDefinitionMap.put("/druid/**", "anon");
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/user/readylogin", "anon");
        filterChainDefinitionMap.put("/user/login", "anon");
        filterChainDefinitionMap.put("/**", "authc,kickout");
        shiroFilterFactoryBean.setLoginUrl("/user/unauth");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        securityManager.setSessionManager(sessionManager());
        securityManager.setCacheManager(cacheManager());
//        securityManager.setRememberMeManager(rememberMeManager());//配置记住我
        return securityManager;
    }

    /**
     * shiro的Realm
     */
    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCachingEnabled(true);
        //由于系统中的认证匹配存在随机数  不使用缓存机制
//        myShiroRealm.setAuthenticationCachingEnabled(true);//启用身份验证缓存，即缓存AuthenticationInfo信息，默认false
//        myShiroRealm.setAuthenticationCacheName("authenticationCache");//缓存AuthenticationInfo信息的缓存名称
        myShiroRealm.setAuthorizationCachingEnabled(true);//启用授权缓存，即缓存AuthorizationInfo信息，默认false
        myShiroRealm.setAuthorizationCacheName("authorizationCache");//缓存AuthorizationInfo信息的缓存名称
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());//配置自定义密码比较器
        return myShiroRealm;
    }
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        RetryLimitHashedCredentialsMatcher hashedCredentialsMatcher = new RetryLimitHashedCredentialsMatcher();
        hashedCredentialsMatcher.setRedisManager(redisManager());
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(1);//散列的次数，比如散列一次次，相当于 md5(md5(""));
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(false);//设置此系统存储的凭据哈希是否为十六进制编码的指示器。
        return hashedCredentialsMatcher;
    }

    /**
     * cookie管理对象;记住我功能,rememberMe管理器
     */
    @Bean
    public CookieRememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
        cookieRememberMeManager.setCipherKey(Base64.decode("WevVhmFLUs0KTA3Kprsdag=="));
        return cookieRememberMeManager;
    }
    @Bean
    public SimpleCookie rememberMeCookie(){
        //这个参数是cookie的名称，对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        simpleCookie.setHttpOnly(true);
        simpleCookie.setPath("/");
        simpleCookie.setMaxAge(2592000);
        return simpleCookie;
    }
    @Bean
    public FormAuthenticationFilter formAuthenticationFilter(){
        FormAuthenticationFilter formAuthenticationFilter = new FormAuthenticationFilter();
        formAuthenticationFilter.setRememberMeParam("rememberMe");//对应前端的checkbox的name = rememberMe
        return formAuthenticationFilter;
    }

    /**
     * 缓存权限
     */
    @Bean
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        redisCacheManager.setPrincipalIdFieldName("userName"); //redis中针对不同用户缓存
        redisCacheManager.setExpire(1200);//用户权限信息缓存时间 20分钟
        return redisCacheManager;
    }
    /**
     * 缓存用户
     * */
    @Bean
    public OtherCacheManager otherCacheManager() {
        OtherCacheManager otherCacheManager = new OtherCacheManager();
        otherCacheManager.setRedisManager(redisManager());
        otherCacheManager.setExpire(1200);//20分钟
        return otherCacheManager;
    }
    /**
     * 缓存session
     */
    @Bean
    public SessionManager sessionManager() {
        ShiroSessionManager mySessionManager = new ShiroSessionManager();
        Collection<SessionListener> listeners = new ArrayList<SessionListener>();
        listeners.add(sessionListener()); //配置监听
        mySessionManager.setSessionListeners(listeners);
        mySessionManager.setSessionDAO(redisSessionDAO());
        mySessionManager.setSessionIdCookieEnabled(false);//拒绝cookie
        mySessionManager.setSessionIdCookie(simpleCookie());
        mySessionManager.setSessionIdUrlRewritingEnabled(false);//取消url 后面的 JSESSIONID
        mySessionManager.setSessionFactory(sessionFactory());
        mySessionManager.setGlobalSessionTimeout(1200000);//session超时20分钟时间。
        mySessionManager.setSessionIdUrlRewritingEnabled(false); //取消url 后面的 JSESSIONID
        return mySessionManager;
    }
    @Bean("sessionFactory")
    public ShiroSessionFactory sessionFactory(){
        ShiroSessionFactory sessionFactory = new ShiroSessionFactory();
        return sessionFactory;
    }
    @Bean
    public SessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        redisSessionDAO.setExpire(1300); //session在redis中的保存时间21分钟,最好大于session会话超时时间
        return redisSessionDAO;
    }
    @Bean
    public SimpleCookie simpleCookie() {
        SimpleCookie sCookie = new SimpleCookie();
        sCookie.setName("SHIRO-COOKIEID");
        sCookie.setHttpOnly(true);
        sCookie.setMaxAge(-1);//-1表示浏览器关闭时失效此Cookie
        return sCookie;
    }


    /**
     * redis管理工具
     */
    @Bean
    public RedisManager redisManager() {
        return new RedisManager();
    }

    /**
     * 并发登录控制filter
     * @return
     */
    @Bean
    public KickoutSessionControlFilter kickoutSessionControlFilter(){
        KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
        kickoutSessionControlFilter.setSessionManager(sessionManager());
        kickoutSessionControlFilter.setRedisManager(redisManager());
        kickoutSessionControlFilter.setKickoutAfter(false);
        kickoutSessionControlFilter.setMaxSession(1);
        kickoutSessionControlFilter.setKickoutUrl("/user/unauth");
        return kickoutSessionControlFilter;
    }


    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * Shiro生命周期处理器
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 自动创建代理
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }
    /**
     * 注册全局异常处理
     */
    @Bean(name = "exceptionHandler")
    public HandlerExceptionResolver handlerExceptionResolver() {
        return new MyExceptionHandler();
    }


    /**
     * 配置session监听
     */
    @Bean("sessionListener")
    public ShiroSessionListener sessionListener(){
        ShiroSessionListener sessionListener = new ShiroSessionListener();
        return sessionListener;
    }

    /**
     * 判断静态文件
     * */
    @Bean
    public ResourceUrlProvider resourceUrlProvider(){
        ResourceUrlProvider resourceUrlProvider = new ResourceUrlProvider();
        return resourceUrlProvider;
    }
}